/*Basile Clout, May 2007
*
* Determine bandwidth between client and server
* using Unix sockets 
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>

#include <string.h>
#include <sys/uio.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdarg.h>

#include "bandwidth.h"


void usage(char *basename){
	printf("Usage: %s [-s | -c host]\n", basename);
}

void *server(){
	
	int sockfd;
	char name[80];
	struct hostent *remote_host; 
	struct sockaddr_in my_addr, remote_addr;
	socklen_t sockaddr_size = sizeof(struct sockaddr_in);
	char confirmation[1];
	int one=1;
	int fd;
	int buffer_size;
	int i,j;
	char latbuf[1];

	char buffer_rcv[TCP_TEST_SIZE];
	int rd,tot=0;

	gethostname(name, sizeof(name));
	printf("%s: Server mode.\n",name);
	
	/* Creating server socket */
	if((sockfd = socket(AF_INET, SOCK_STREAM,0)) == -1){
		perror("Error when creating server socket: ");
		exit(1);
	}
	
	if(setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR,(void *)&one, (socklen_t)sizeof(int)) == -1){
		perror("Error setting REUSEADDR server socket option: ");
		exit(1);
	}
	
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(PORT);
	my_addr.sin_addr.s_addr = INADDR_ANY;
	memset(&(my_addr.sin_zero), '\0', 8);
	
	if(bind(sockfd,(struct sockaddr *) &my_addr, sizeof (struct sockaddr)) == -1){
		perror("Error when binding server socket: ");
		exit(1);
	}

	if(listen(sockfd, BACKLOG) == -1){
	    perror("Error opening listen on server socket: ");
	    exit(-1);
	}

	while(1) {
		
		if ((fd = accept(sockfd, (struct sockaddr *) &remote_addr, &sockaddr_size)) == -1) {
			perror ("Error accepting remote ip: ");
			continue;
		}
		
		if (!fork ()) {
			
			close (sockfd);	
			
			buffer_size = TCP_BUFFER_SIZE;
			setsockopt (fd, SOL_SOCKET, SO_SNDBUF, (char *) &buffer_size, sizeof (buffer_size));
			/* Measure bandwidth server side*/
			while(tot<TCP_TEST_SIZE){
				rd = read(fd, buffer_rcv + tot, TCP_TEST_SIZE - tot );
				if (rd==0)
					break;
				else if(rd < 0){
				    perror("Error server when reading socket: ");
				    exit(-1);
				}
				tot += rd;
			}

			write(fd,confirmation,1);           /* Needed to get a correct bandwidth. Problem when latency is important ... */
			/* Measure latency client side*/
			for(j=0;j<LAT_NB_TESTS;j++){
				for (i=0;i<LAT_NB_TESTS;i++){
					read(fd,latbuf,1);
					write(fd,latbuf,1);
				}
			}

			if((remote_host = gethostbyaddr(&remote_addr.sin_addr, sizeof(remote_addr.sin_addr),AF_INET)) == NULL){
				herror("Error gethostbyaddr for remote (client) host");
				exit(1);
			}
			printf("Reply request from %s (%s)\n",remote_host->h_name,inet_ntoa(remote_addr.sin_addr));fflush(stdout);
			close (fd);
			return 0; /* Destroy child */
		}
		
		close (fd);
	}
}


int client(char *ip){
	
	struct hostent *host;
	struct sockaddr_in server_addr;
	struct in_addr *rem_ip;
	char name[80];
	int sockfd;
	int buffer_size;
	struct timeval t1,t2,t3,t4;
	double delta;
	char latbuffer[1];
	int j;
	double xrtt, min_xrtt;
	
	char buffer_send[TCP_TEST_SIZE];
	int nb = TCP_TEST_SIZE/TCP_PIECE_SIZE;
	int tot=0, cnt=0;
	int i=0;

	
	if((host = gethostbyname(ip)) == NULL ) {
		herror("Error with gethostbyname(ip): ");
		exit(1);
	}
	rem_ip = (struct in_addr*)host->h_addr;                      /* remote ip */
	
	gethostname(name, sizeof(name));
	if((host = gethostbyaddr(rem_ip, sizeof(*rem_ip), AF_INET)) == NULL){
		herror("Error with gethostbyaddr");
		exit(1);
	}
	printf("%s: Client mode, connected to %s (%s). \n",name,host->h_name,inet_ntoa(*rem_ip));
	
	
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		perror("Error when creating server socket: ");
		exit(1);
	}
	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr = *((struct in_addr *) host->h_addr);
	memset(&(server_addr.sin_zero),'\0',8);
	
	if(connect(sockfd, (struct sockaddr *) &server_addr, sizeof(struct sockaddr)) == -1){
		perror("Error connecting server socket: ");
		exit(1);
	}

	buffer_size=TCP_TEST_SIZE;
	if(setsockopt (sockfd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)) == -1){
		perror("Error setting SO_SNDBUF client socket option: ");
		exit(1);
	}

	/* Measure bandwidth */
	CLOCK(t1);
	for(i=0;i<nb;i++){
		cnt = write(sockfd, buffer_send + i*TCP_PIECE_SIZE, TCP_PIECE_SIZE);
		if (cnt<0){
		    perror("Error client writing packets: ");
		    exit(-1);
		}
		tot += cnt;    
	}
	read(sockfd,latbuffer,1);
	CLOCK(t2);
	delta = CLOCK_DIFF(t2,t1);
	

	/* Measure latency */
	xrtt=min_xrtt=1000000;

	for(j=0;j<LAT_NB_TESTS;j++){
		CLOCK(t3);
		for(i=0;i<LAT_NB_TESTS;i++){
			write(sockfd,latbuffer,1);
			read(sockfd,latbuffer,1);
		}
		CLOCK(t4);
		xrtt=CLOCK_DIFF(t4,t3);
		if(xrtt < min_xrtt)
			min_xrtt = xrtt;
	}

	printf("\tLatency=%.3fms\n",min_xrtt*500/LAT_NB_TESTS);

	//printf("delta=%.3f, min_xrtt=%.3f\n", delta, min_xrtt/LAT_NB_TESTS);
	
	int nb_packets = (TCP_PIECE_SIZE/1480)*(TCP_TEST_SIZE/TCP_PIECE_SIZE); /* Add overhead bytes. Each packet sent is at most 1480 bytes long with 48 bytes of header */
	tot = tot + nb_packets*48;

	//int param=2;
	//delta = delta - param*(min_xrtt)/LAT_NB_TESTS;      /* Use the latency to deduce the correct bandwidth (different from the apparent bandwidth that includes the latency) */
                                                /* Corresponds to the one character confirmation */
                                                /* param is a heuristic parameter to take into account latency */
	//printf("%d bytes sent in %f seconds\n",tot, delta);
	printf("\tBandwidth to %s: %.3f Mbytes/s (%.3f MBits/s).\n",ip, ((double)tot/(delta*1000000)),((double)tot/(delta*1000000)*8));
	fflush(stdout);	

	return 0;
}
     



int main (int argc, char * argv[])
{
	
	char *ip;
	
	if (argc < 2 || argc >3){
	    usage(argv[0]);
	    exit(-1);
	}
	
	if ( (argc==2) && (!strcmp(argv[1],"-s")))
	    server();
	else if ( (argc==3) && (!strcmp(argv[1],"-c")) ){
		ip = argv[2];
		client(ip);
	}else{
	    usage(argv[0]);
	    exit(-1);
	}

	exit(0);
}

